\chapter{类型和类}
在这一章中，我们将介绍Haskell中两个最基本的概念：类型和类。我们首先解释什么是类型以及在Haskell中如何使用它，然后介绍一些基本类型以及使用这些基本类型构造更大类型的方法，详细讨论函数类型，最后介绍一下多态类型和类型类的概念。

\section{基本概念}
\textit{类型}是一组相关值的集合。例如，类型$Bool$包含两个逻辑值$False$和$True$，而类型$Bool
\rightarrow
Bool$则包含了所有将$Bool$类型参数映射为$Bool$类型结果的函数，如逻辑非函数$not$。我们使用记法$v::T$表示\textit{v}是类型\textit{T}的一个值，并且可以说\textit{v}“具有类型”\textit{T}。例如：

\noindent\hspace*{1cm} $False :: Bool$\\
\hspace*{1cm} $True :: Bool$\\
\hspace*{1cm} $not :: Bool \rightarrow Bool$

一般地说，符号::也可以用于尚未被求值的表达式，这种情况下，$e::T$意思是对表达式$e$求值将产生一个类型$T$的值。例如：

\noindent\hspace*{1cm} $not~False :: Bool$\\
\hspace*{1cm} $not~True :: Bool$\\
\hspace*{1cm} $not~(not~False):: Bool$

在Haskell中，每个表达式必须有一个类型，该类型通过一个先于表达式求值的过程计算得到，这个过程被称为\textit{类型推断}。这个过程的关键在于一个函数应用的类型规则，其中规定如果$f$是一个将$A$类型参数映射为$B$类型结果的函数，且$e$是一个类型$A$的表达式，那么$f~~e$具有类型$B$：\\

\begin{center}
$\dfrac{f::A \rightarrow B~~e::A}{f~e::B}$
\end{center}

例如，$not~False::Bool$可通过这样的规则推断，该规则使用了这样的事实：$not::Bool
\rightarrow
Bool$和$False::Bool$。另一方面，表达式$not~3$通过上述规则无法推断类型，因为这需要$3::Bool$，但$3$不是一个逻辑值，这是无效的。像$not~3$这样的表达式无法确定类型，也可以说成是包含了一个类型错误，被视为无效表达式。

由于类型推断在求值过程之前，所以Haskell程序是\textit{类型安全}的，也就是说在求值过程中不会发生类型错误。实际上，类型推断能检查出程序中所占错误比例较高的一类错误，它也是Haskell最有用的特点之一。但是注意使用类型推断并不能消除发生在求值阶段的其他类错误的可能性，比如，表达式$1~`div`~0$可以通过类型推断检查，但在求值阶段报错，因为被$0$除的行为是未定义的。

类型安全的不足之处在于一些求值阶段成功的表达式却因类型原因而被拒绝。例如，条件表达式\textbf{if} 
$True$ \textbf{then} $1$ \textbf{else} $False$求值结果为$1$,
但是因包含一个类型错误而被视为无效表达式。
特别是，条件表达式的类型推断规则要求所有可能的结果都具有相同的类型，而在这里例子中，第一种结果为$1$，是一个数字类型，而第二个结果是$False$，是一个逻辑值类型。在实践中，程序员很快就学会了如何在类型系统的限制下工作以及如何避免这些问题。

在Hugs系统中，任意表达式的类型都可以通过$:type$显示出来，例如：

\noindent\hspace*{1cm} $> :type~not$\\
\hspace*{1cm} $not :: Bool \rightarrow Bool$\\
\hspace*{1cm} \\
\hspace*{1cm} $> :type~not~False$\\
\hspace*{1cm} $not~False :: Bool$\\
\hspace*{1cm} \\ 
\hspace*{1cm} $> :type~not~3$\\
\hspace*{1cm} $Error$

\section{基本类型}
\noindent Haskell提供了很多内置到语言中的基本类型，其中最常用的类型描述如下：
\\
\\
$Bool$~-~ \textbf{逻辑值}\\
这个类型包含了两个逻辑值$False$和$True$。
\\
\\
$Char$~-~\textbf{字符}\\
这个类型包含了普通键盘上提供的所有单个字符，如'$a$'，'$A$'，'$3$'和\verb|'_'|，以及拥有特殊效果的控制字符，如\verb|'\n'|（移动到新的一行）和\verb|'\t'|（移动到下一个制表位）。正如其他大多数编程语言的标准一样，单个字符必须用单引号'~'括起。
\\
\\
$String$~-~\textbf{字符串}\\
这个类型包含了所有字符序列，诸如"$abc$"，"$1~+~2~=~3$"以及空字符串""。同样正如其他大多数编程语言的标准，字符串必须用双引号" "括起。
\\
\\
$Int$~-~\textbf{固定精度整数}\\
这个类型包含诸如$-100$，$0$以及$999$这样的整数，计算机以固定大小的内存存储这些值。例如，Hugs系统中$Int$类型的取值范围在$-2^{31}$和$2^{31}-1$之间。超出这个范围将得到非预期的结果。例如，在Hugs系统中对\verb|2^31::Int|（使用::将强制结果为$Int$类型而不是其他的数值类型）进行求值将得到一个负值，这是不正确的。
\\
\\
$Integer$~-~\textbf{任意精度整数}\\
该类型包含所有的整数，我们使用足够多的内存储存这个类型的值，从而避免了对该类型值的范围强加上限和下限。例如，使用任何Haskell系统对\verb|2^31::Integer|求值都可以得到正确的结果。

除了对内存和精度的需求不同外，在$Int$和$Integer$之间数字类型的选择还是性能考量之一。特别是，大多数电脑都内置了用来处理固定精度整数的硬件，而任意精度的整数通常必须被看成数字序列，通过速度较慢的软件来处理。
\\
\\
$Float$~-~\textbf{单精度浮点数}\\
这个类型包含带小数点的数字，诸如$-12.34$，$1.0$以及$3.14159$，计算机以固定大小内存存储这些值。浮点一词源于这样一个事实：即小数点后允许的数字位数取决于数的大小。例如使用Hugs对$sqrt
~2 ::
Float$求值结果为$1.41421$（库函数$sqrt$用于计算一个数的平方根），其中小数点后有五位数字;而对$sqrt~99999::Float$求值结果为$316.226$，小数点后则只有三位数字。采用浮点数编程是一个专家话题，需要认真对待舍入误差。在本书入门性的文字中，我们将很少说到这种类型。

最后，我们注意到一个单个数字可能拥有不止一种数值类型。例如，$3 :: Int$，$3 ::
Integer$和$3 ::
Float$对于数字$3$来说都是有效的类型。这就提出了一个有趣的问题：这些数字在类型推断过程中究竟应该被分配什么类型？这个问题将在本章后面考量类型类时回答。

\section{列表类型}
\textit{列表}是一个由相同类型元素组成的序列，其元素放在方括号中，并使用逗号分隔。我们将元素类型为$T$的所有\textit{列表}类型记作[$T$]。比如：

\noindent\hspace*{1cm} $[False,~True,~False]~::~[Bool]$\\
\hspace*{1cm} $[’a’,~’b’,~’c’,~’d’]~::~[Char]$\\
\hspace*{1cm} $["One",~"Two",~"Three"]~::~[String]$

一个列表中元素的个数称为列表的\textit{长度}。长度为0的列表$[~~]$称为空列表，而长度为1的列表，如$[False]$和$['a']$，称为单件列表。注意$[~[~~]~]$和$[~~]$是两个不同的列表，前者是一个单件列表，组成该列表的唯一的元素是一个空列表，而后者则仅仅是一个空列表。

关于列表类型这里有三点需进一步注意。首先，一个列表的类型中没有传达其长度信息。例如，$[False,~
True]$和$[False,~True,~
False]$两者都是$[Bool]$类型，即使他们的长度不同。其次，列表的元素没有类型限制。目前我们局限在我们所能给出的例子范围内，因为到目前为止我们介绍的唯一的非基本类型就是列表类型，但是我们可以定义由列表类型元素组成的列表，例如：

\noindent\hspace*{1cm} $[[’a’,~’b’],~[’c’,~’d’,~’e’]]~::~[[Char]]$

最后，对一个列表的长度没有任何限制。特别是，正如我们将在第12章看到的那样，由于在Haskell中使用了惰性求值，具有无限长度的列表是自然且实用的。

\section{元组类型}
\textit{元组}是一个由类型可能不同的元素组成的有限长度序列，其元素放在圆括号中，并使用逗号分隔。我们用($T_1,~T_2,~...,~T_n$)表示所有元组的类型。对于从$i$到$n$范围内的任意值$i$，第$i$个元素具有类型$T_i$。例如：

\noindent\hspace*{1cm} $(False,~True)~::~(Bool,~Bool)$\\
\hspace*{1cm} $(False,~’a’,~True)~::~(Bool,~Char,~Bool)$\\
\hspace*{1cm} $("Yes",~True,~’a’)~::~(String,~Bool,~Char)$

一个元组中元素的个数称为\textit{元数}（arity）。元数为0的元组
$(~)$称为空元组，元数为2的元组称为二元组，元数为3的元组称为三元组，等等。元数为1的元组，例如($False$)，是不允许使用的，因为它们将与显式设置求值顺序的括号的使用相冲突，如表达式$(1~+~2)~*~3$。

与列表类型一样，关于元组类型也有三点需进一步注意。首先，元组的类型传达了元数信息。例如，类型($Bool,
~Char$)包含了所有第一个元素为$Bool$类型且第二个元素为$Char$类型的二元组。其次，元组中的元素没有类型限制。例如，我们可以定义由元组类型元素组成的元组，由列表类型元素组成的元组以及由元组类型元素组成的列表：

\noindent\hspace*{1cm} $(’a’,~(False,~’b’))~::~(Char,~(Bool,~Char))$\\
\hspace*{1cm} $([’a’,~’b’],~[False,~True])~::~([Char],~[Bool])$\\
\hspace*{1cm} $[(’a’,~False),~(’b’,~True)]~::~[(Char,~Bool)]$

最后，注意元组必须具有有限元数，以保证元组类型总是能在求值过程之前被计算出来。

\section{函数类型}
\textit{函数}是一个从一种类型参数到另外一种类型结果的映射。我们用$T_1
\rightarrow T_2$表示所有将$T_1$类型参数映射为$T_2$类型结果的函数。例如：

\noindent\hspace*{1cm} $not~::~Bool \rightarrow Bool$\\
\hspace*{1cm} $isDigit~::~Char \rightarrow Bool$

（库函数$isDigit$用于判断一个字符是否是一个数字位）由于对函数的参数类型和结果类型没有任何限制，使用单一参数和结果的函数的简单概念就足以应付多个参数和结果的情况，只需将多个值使用列表或元组打包即可。例如，我们下面定义函数$add$，用于计算一个整数二元组的元素之和；定义函数$zeroto$，用于返回一个从$0$到给定上限值的整数列表：

\noindent\hspace*{1cm} $add~::~(Int,~Int) \rightarrow Int$\\
\hspace*{1cm} $add~(x~,~y)~=~x~+~y$\\
\hspace*{1cm} $zeroto~::~Int \rightarrow [Int]$\\
\hspace*{1cm} $zeroto~n~=~[0~. .~n]$

在这些例子中我们遵循了Haskell将函数类型放在函数定义之前作为参考文档的惯例。系统将检查由用户手工提供的类型与类型推断自动计算出的类型两者之间的一致性。

注意没有限制要求函数对它们的参数类型必须要有预期结果。这样一来，对于函数的某些参数来说，其结果可能是未定义的。比如当列表为空时，库函数$head$从列表中选出第一个元素的行为就是未定义的。

\section{Curried函数}
函数可以自由地将函数作为结果返回，利用这个事实，接受多个参数的函数也可以使用另外一种也许并不显而易见的方式处理。例如，考虑下面的定义：

\noindent\hspace*{1cm} $add'~::~Int \rightarrow (Int \rightarrow Int)$\\
\hspace*{1cm} $add'~x~y~=~x~+~y$

函数类型表明$add'$是一个函数，其接受的参数类型为$Int$，返回结果为一个类型为$Int \rightarrow Int$的函数。定义本身表明$add'$接受一个整数$x$为参数，后面跟着一个整数$y$，返回结果为$x~+ ~y$。更确切地说，$add'$接受整数$x$为参数，返回一个接受整数$y$为参数且返回$x~+~y$的函数。

注意函数$add'$产生的最终结果与上一节中的函数$add$相同。然而函数$add$将其两个参数打包为一个二元组后一同处理，而函数$add'$则一次仅接受处理一个参数，正如这两个函数的类型所反映的那样：

\noindent\hspace*{1cm} $add~::~(Int,~Int) \rightarrow Int$\\
\hspace*{1cm} $add'~::~Int \rightarrow (Int \rightarrow Int)$

对于有两个以上参数的函数，也可以使用同样的技术处理，通过返回以函数为返回值的函数，等等。例如，函数$mult$接受三个参数，每次接受一个，并返回它们的乘积，其定义如下：

\noindent\hspace*{1cm} $mult~::~Int \rightarrow (Int \rightarrow (Int \rightarrow Int))$\\
\hspace*{1cm} $mult~x~y~z~=~x~*~y~*~z$

这个定义表明$mult$接受一个整数$x$为参数并返回一个函数，后者依次接受一个整数$y$为参数并返回另外一个函数，最后这个函数接受整数$z$为参数，并最终返回结果$x~*~y~*~z$。

诸如$add'$和$mult$这样的每次接受一个参数的函数被称为$curried$。除了本身有吸引力之外，curried函数也比接受元组作为参数的函数更加灵活，因为一些有用的函数通常可以通过\textit{部分应用}参数不完整的curried函数来实现。例如，一个完成递增功能的函数可以通过curried函数$add'$的部分应用$add'~1 :: Int \rightarrow Int$实现，后者仅需要两个参数中的一个。

为避免在使用curried函数工作时过度使用括号，我们采纳了两个简单的惯例。首先，类型中使用的箭头$\rightarrow$是右结合的，例如：

\noindent\hspace*{1cm} $Int \rightarrow Int \rightarrow Int \rightarrow Int$

意为

\noindent\hspace*{1cm} $Int \rightarrow (Int \rightarrow (Int \rightarrow Int))$

然而使用空格表示的函数应用则是左结合的，例如：

\noindent\hspace*{1cm} $mult~x~y~z$

意为

\noindent\hspace*{1cm} $((mult~x)~y)~z$

除非显式需要元组，Haskell中的所有接受多个参数的函数一般都会被定义成curried函数，并且使用上面的两个惯例来减少需要使用的括号的数量。

\section{多态类型}

库函数\textit{length}用于计算任意列表的长度，无论列表中的元素是什么类型的。比如，它可以用于计算整型列表、字符串型列表甚至是函数类型列表的长度：\\
\hspace*{1cm} > $length~[1,~3,~5,~7]$\\
\hspace*{1cm} $4$\\
\hspace*{1cm} > $length~["Yes",~"No"]$\\
\hspace*{1cm} $2$\\
\hspace*{1cm} > $length~[isDigit~,~isLower~,~isUpper]$\\
\hspace*{1cm} $3$

通过在类型中包含\textit{类型变量}，使得将$length$应用于由任意类型元素组成的列表的想法得以精确实现。类型变量必须以小写字母开头，通常命名为\textit{a，b，c}等。例如，\textit{length}的类型如下：\\
\hspace*{1cm} $length :: [a] \rightarrow Int$

即对任意类型\textit{a}，函数\textit{length}具有类型$[a]
\rightarrow
Int$。包含一个或多个类型变量的类型被称作\textit{多态的}（“多种形式"），使用这种类型的表达式也是多态的。因此$[a] \rightarrow
Int$是一个多态类型，\textit{length}是一个多态函数。更普遍的是，标准Prelude中提供的很多函数都是多态的，例如：\\
\hspace*{1cm} $fst~::~(a,~b) \rightarrow a$\\
\hspace*{1cm} $head~::~[a] \rightarrow a$\\
\hspace*{1cm} $take~::~Int \rightarrow [a] \rightarrow [a]$\\
\hspace*{1cm} $zip~::~[a] \rightarrow [b] \rightarrow [(a,~b)]$\\
\hspace*{1cm} $id~::~a \rightarrow a$

\section{重载类型}

算术运算符$+$用于计算任意两个相同数值类型数的和。例如，它可以用来计算两个整数的和或两个浮点数的和：

\noindent\hspace*{1cm} $> 1~+~2$\\
\hspace*{1cm} $> 3$\\

\noindent\hspace*{1cm} $> 1.1~+~2.2$\\
\hspace*{1cm} $> 3.3$

通过在类型中包含\textit{类约束}，使得将$+$应用于任意数值类型数字的想法得以精确实现。 类约束写成$C~a$，其中$C$是类的名字，$a$是一个类型变量。例如，$+$的类型如下： 

\noindent\hspace*{1cm} $(+)~::~Num~a \Rightarrow  a \rightarrow a \rightarrow a$

即对于任意一个数值类型类$Num$的实例类型$a$，函数$(+)$具有类型$a \rightarrow a
\rightarrow
a$。（将操作符括起来将之转换为一个curried函数，下一章中将详细解释其中缘由。）一个包含一个或多个类约束的类型称为\textit{重载的}，使用这种类型的表达式也是重载的。因此，$Num~a \Rightarrow  a \rightarrow a \rightarrow a$是一个重载类型，$(+)$是一个重载函数。更普遍的是，标准Prelude库中提供的大多数数值函数都是重载的，例如：

\noindent\hspace*{1cm} $(-)~::~Num~a \Rightarrow  a \rightarrow a \rightarrow a$\\
\hspace*{1cm} $(*)~::~Num~a \Rightarrow  a \rightarrow a \rightarrow a$\\
\hspace*{1cm} $(negate)~::~Num~a \Rightarrow  a \rightarrow a$\\
\hspace*{1cm} $(abs)~::~Num~a \Rightarrow  a \rightarrow a$\\
\hspace*{1cm} $(signum)~::~Num~a \Rightarrow  a \rightarrow a$

此外，数值本身也是重载的。例如，$3::Num~a \Rightarrow a$意为对于任意数值类型$a$，数字3具有类型$a$。

\section{基本类}
回顾一下，一个类型是一组相关值的集合。基于这个概念，一个\textit{类}是一组支持重载操作的类型的集合，这些重载操作被称为\textit{方法}。Haskell提供一定数量的内置的基本类，下面描述了其中最常用的类：
\\
\\
$Eq$~-~ \textbf{相等类型}\\
包含在这个类中的类型的值可以使用下面两个方法进行相等和不等比较：

\noindent\hspace*{1cm} $(==) :: a \rightarrow a \rightarrow Bool$\\
\hspace*{1cm} $(\neq) :: a \rightarrow a \rightarrow Bool$

所有的基本类型$Bool, Char, String, Int,
Integer$和$Float$都是Eq类的实例。列表和元组类型也是一样，如果它们的元素类型是Eq类的实例，例如：

\noindent\hspace*{1cm} $> False == False$\\
\hspace*{1cm} $True$\\

\noindent\hspace*{1cm} $>'a'~==~'b'$\\
\hspace*{1cm} $False$\\

\noindent\hspace*{1cm} $> "abc"~==~"abc"$\\
\hspace*{1cm} $True$\\

\noindent\hspace*{1cm} $> [1,~2]~==~[1,~2,~3]$\\
\hspace*{1cm} $False$\\

\noindent\hspace*{1cm} $> ('a',~False)~==~('a',~False)$\\
\hspace*{1cm} $True$\\

\noindent$Ord$~-~ \textbf{有序类型}\\
包含在这个类中的类型都是Eq类的实例，除此之外这些类型的值都是（线性）有序的，可以通过以下六个方法进行比较和处理：

\noindent\hspace*{1cm} $(<) :: a \rightarrow a \rightarrow Bool$\\
\hspace*{1cm} $(\leq) :: a \rightarrow a \rightarrow Bool$\\
\hspace*{1cm} $(>) :: a \rightarrow a \rightarrow Bool$\\
\hspace*{1cm} $(\geq) :: a \rightarrow a \rightarrow Bool$\\
\hspace*{1cm} $(min) :: a \rightarrow a \rightarrow a$\\
\hspace*{1cm} $(max) :: a \rightarrow a \rightarrow a$\\

所有的基本类型$Bool, Char, String, Int,
Integer$和$Float$都是\textit{Ord}类的实例。列表和元组类型也是一样，如果它们的元素类型是Ord类的实例，例如：

\noindent\hspace*{1cm} $> False~<~True$\\
\hspace*{1cm} $True$\\

\noindent\hspace*{1cm} $> min~'a'~'b'$\\
\hspace*{1cm} $'a'$\\

\noindent\hspace*{1cm} $> "elegant"~<~"elephant"$\\
\hspace*{1cm} $True$\\

\noindent\hspace*{1cm} $> [1,~2,~3]~<~[1,~2]$\\
\hspace*{1cm} $False$\\

\noindent\hspace*{1cm} $> ('a',~2)~<~('b',~1)$\\
\hspace*{1cm} $True$\\

\noindent\hspace*{1cm} $> ('a',~2)~<~('a',~1)$\\
\hspace*{1cm} $False$\\

请注意，字符串，列表和元组都是按字典序排序的；
也就是说与单词在字典中的顺序一样。例如，两个相同类型的二元组是有序的。如果它们的第一个元素是有序的，则无须考虑它们的第二个元素。或者如果它们的第一个元素相等，那么它们的第二个元素则必须是有序的。
\\
\\
\noindent$Show$~-~ \textbf{可显示的类型}\\
包含在这个类中的类型的值都可以通过下面方法转换为字符串：

\noindent\hspace*{1cm} $show :: a \rightarrow String$

所有的基本类型$Bool, Char, String, Int,
Integer$和$Float$都是\textit{Show}类的实例。列表和元组类型也是一样，如果它们的元素是Show类的实例，例如：

\noindent\hspace*{1cm} $> show~False$\\
\hspace*{1cm} "False"\\

\noindent\hspace*{1cm} > $show~~'a'$\\
\hspace*{1cm} "~'$a$'~"\\

\noindent\hspace*{1cm} > $show~123$\\
\hspace*{1cm} "$123$"\\

\noindent\hspace*{1cm} > $show$~[1,~2,~3]\\
\hspace*{1cm} "$[1,~2,~3]$"\\

\noindent\hspace*{1cm} > show~($'a',~False$)\\
\hspace*{1cm} "$('a',~False)$"\\

\noindent$Read$~-~ \textbf{可读的类型}\\
这个类与类Show是一对，包含在这个类中的类型的值可以通过下面方法从字符串转换得到：

\noindent\hspace*{1cm} $read:: String \rightarrow a$

所有的基本类型$Bool, Char, String, Int,
Integer$和$Float$都是\textit{Read}类的实例。列表和元组类型也是一样，如果它们的元素是Read类的实例，例如：

\noindent\hspace*{1cm} $> read~"False"~::~Bool$\\
\hspace*{1cm} $False$\\

\noindent\hspace*{1cm} > $read~~'a'~::~Char$\\
\hspace*{1cm} '$a$'\\

\noindent\hspace*{1cm} > $read~123~::~Int$\\
\hspace*{1cm} $123$\\

\noindent\hspace*{1cm} > $read$~~"[1,~2,~3]"~::~[Int]\\
\hspace*{1cm} $[1,~2,~3]$\\

\noindent\hspace*{1cm} > $read$ "($'a',~False$)"\\
\hspace*{1cm} $('a',~False)$\\

例子中使用$::$决定结果的类型。然而在实际中，通常可通过上下文自动推断出必要的类型信息。例如，表达式$not~(read~
"False")$不需要显式的类型信息，因为使用逻辑非的$not$的程序暗示了$read~
"False"$必须具有$Bool$类型。

注意，如果参数不符合语法要求，那么$read$的结果将是未定义的。例如，表达式$not~(read~
"hello")$在求值时会产生一个错误，因为$"hello"$被$read$的结果不是一个逻辑值。
\\
\\
\noindent$Num$~-~ \textbf{数字类型}\\
包含在这个类中的类型都是$Eq$类和$Show$类的实例，除此之外这些类型的值都是数字，可以通过以下六个方法进行处理：


\noindent\hspace*{1cm} $(+)~::~a \rightarrow a \rightarrow a$\\
\hspace*{1cm} $(-)~::~a \rightarrow a \rightarrow a$\\
\hspace*{1cm} $(*)~::~a \rightarrow a \rightarrow a$\\
\hspace*{1cm} $(negate)~::~a \rightarrow  a$\\
\hspace*{1cm} $(abs)~::~a \rightarrow  a$\\
\hspace*{1cm} $(signum)~::~a \rightarrow a$

（$negate$方法返回一个数的负数，$abs$返回绝对值，而$signum$则返回数的符号性。）
基本类型$Int, Integer$和$Float$都是Num类的实例，例如：

\noindent\hspace*{1cm} $> 1~+~2$\\
\hspace*{1cm} $3$\\

\noindent\hspace*{1cm} > $1.1~+~2.2$\\
\hspace*{1cm} $3.3$\\

\noindent\hspace*{1cm} > $negate~3.3$\\
\hspace*{1cm} $-3.3$\\

\noindent\hspace*{1cm} > $abs~(-3)$\\
\hspace*{1cm} $3$\\

\noindent\hspace*{1cm} > $signum~(-3)$\\
\hspace*{1cm} $3$\\

注意Num类没有提供除法，但是正如我们即将要看到的，Haskell提供两个特殊的类单独处理除法，一个类用于整数数字，而另外一个用于分数。
\\
\\
\noindent$Integral$~-~ \textbf{整数类型}\\
包含在这个类中的类型都是$Num$类的实例，除此之外这些类型的值都是整数，支持整数除法和整数取余：

\noindent\hspace*{1cm} $div~::~a \rightarrow a \rightarrow a$\\
\hspace*{1cm} $mod~::~a \rightarrow a \rightarrow a$\\

实际中，这两个方法经常写在其两个参数之间，并用单引号括上。基本类型$Int$和$Integer$是Integral类的实例。例如：

\noindent\hspace*{1cm} $> 7~`div`~2$\\
\hspace*{1cm} $3$\\

\noindent\hspace*{1cm} > $7~`mod`~2$\\
\hspace*{1cm} $1$\\

考虑到效率，一些标准Prelude库中既涉及列表又涉及整型的函数（如$length$，$take$和$drop$）被严格应用在$Int$这样的有限精度整数上，而不是应用到任意$Integral$类的实例上。如果需要，这些函数的通用版本在另外一个叫作$List.hs$的库中提供。
\\
\\
\noindent$fractional$~-~ \textbf{分数类型}\\
包含在这个类中的类型都是$Num$类的实例，但除此之外这些类型的值都是非整数，支持小数除法和小数倒数：

\noindent\hspace*{1cm} $(/)~::~a \rightarrow a \rightarrow a$\\
\hspace*{1cm} $(recip)~::~a \rightarrow a$\\

基本类型$Float$是分数类的一个实例，例如：

\noindent\hspace*{1cm} $> 7.0 / 2.0$\\
\hspace*{1cm} $3.5$\\
\\
\noindent\hspace*{1cm} $> recip~2.0$\\
\hspace*{1cm} $0.5$\\

\section{本章备注}
用术语$Bool$表示逻辑值类型是为了纪念\textbf{George
Boole}在符号逻辑领域做出的开创性贡献，而用术语$curried$表示函数一次只接受一个参数是用于纪念\textbf{Haskell
Curry}(Haskell语言本身也是以他命名的)在这类函数领域所做的工作。Haskell报告(25)中给出了关于类型系统的更详尽的描述，提供给专家的正式说明，可以在(20; 6)中找到。

\section{习题}

\begin{enumerate}
\item 下面的值是什么类型？

\noindent\hspace*{1cm} $[’a’, ’b’, ’c’]$\\
\hspace*{1cm} $(’a’, ’b’, ’c’)$\\
\hspace*{1cm} $[(False, ’O’), (True, ’1’)]$\\
\hspace*{1cm} $([False, True ], [’0’, ’1’])$\\
\hspace*{1cm} $[tail , init, reverse ]$

\item 下面的函数是什么类型？

\noindent\hspace*{1cm} $second~xs~=~head~(tail~xs)$\\
\hspace*{1cm} $swap~(x , y) = (y, x )$\\
\hspace*{1cm} $pair~x~y = (x , y)$\\
\hspace*{1cm} $double~x = x * 2$\\
\hspace*{1cm} $palindrome~xs = reverse~xs~==~xs$\\
\hspace*{1cm} $twice~f~x = f~(f~x )$

提示：如果函数定义中使用了重载操作符，注意包含必要的类约束。

\item 使用Hugs检查一下你关于前两个问题的回答

\item 为什么在一般情况下将函数类型都作为Eq类的实例是不可行的？什么时候是可行的？提示：两个类型相同的函数是相等的，如果两个类型相同的函数对于相等的参数始终返回相同的结果，那么这两个函数是相等的。

\end{enumerate}
